struct dirent {
	unsigned char d_name[32];
};

struct builtin_cmd {
    char *name;
    int (*func)(char **);
};

#define NULL 0

#define MAX_INPUT 1024
#define MAX_FILENAME 128
#define MAX_ARGS 64

static char g_command[MAX_INPUT];
static char *g_argv[MAX_ARGS];
static char command_file[MAX_FILENAME];

int echo_cmd(char **args)
{
	int i;

    for (i = 1; args[i] != NULL; i++) {
        printf("%s", args[i]);
        if (args[i + 1] != NULL) {
            printf(" ");
        }
    }
    printf("\r\n");

    return 0;
}

int cd_cmd(char **args)
{
    if (args[1] == NULL) {
        // fprintf(stderr, "cd: missing argument\n");
        return 1;
    }
    
    if (chdir(args[1]) != 0) {
        // perror("cd");
        return 1;
    }
    return 0;
}

int pwd_cmd(char **args)
{
	char cwd[MAX_FILENAME];

    if (getcwd(cwd, sizeof(cwd)) != NULL) {
        printf("%s\r\n", cwd);
        return 0;
    } else {
        // perror("pwd");
        return 1;
    }
}

int exit_cmd(char **args)
{
    exit(0);
    return 0;
}

#define O_CREAT		00000100	/* not fcntl */
int mount_cmd(char **args)
{
	int ret = 0;
	int i;
	int fd;
	char read_buf[32] = {0};
	char write_buf[32] = {0};

	// mount
	// ret = mount("none", "/mnt", "ramfs");
	ret = mount("none", "/mnt", "ext2");
	printf("this is %s(): %d >>> ret = %d\r\n", __func__, __LINE__, ret);

#if 0
	// open
	fd = open("/mnt/start.txt", O_CREAT ,0);
	printf("this is %s(): %d >>> fd = %d\r\n", __func__, __LINE__, fd);

	// write
	for (i = 0; i < sizeof(write_buf); i++) {
		write_buf[i] = i;
	}
	printf("this is %s(): %d >>> final write_buf:\r\n", __func__, __LINE__);
	for (i = 0; i < sizeof(write_buf); i++) {
		printf("%d ", write_buf[i]);		
	}
	printf("\r\n");
	write(fd, write_buf, sizeof(write_buf));

	// read
	read(fd, read_buf, sizeof(read_buf), 0);
	printf("this is %s(): %d >>> final read_buf:\r\n", __func__, __LINE__);
	for (i = 0; i < sizeof(read_buf); i++) {
		printf("%d ", read_buf[i]);		
	}
	printf("\r\n");
#endif

    return 0;
}

int mkdir_cmd(char **args)
{
	int ret;

	printf("this is %s(): %d >>> args[0] = %s\r\n", __func__, __LINE__, args[1]);

	ret = mkdir(args[1], 0);
	printf("this is %s(): %d >>> ret = %d\r\n", __func__, __LINE__, ret);

    return 0;
}

int ls_cmd(char **args)
{
	struct dirent dirent[32];
	int i, dir_num;
	int fd;
	char filename[1024];

	getcwd(filename, sizeof(filename));
	printf("this is %s(): %d >>> filename = %s\r\n", __func__, __LINE__, filename);

	fd = open(filename, 0 ,0);
	printf("this is %s(): %d >>> fd = %d\r\n", __func__, __LINE__, fd);

	dir_num = readdir(fd, dirent);
	for (i = 0; i < dir_num; i++) {
		printf("%s  ", dirent[i].d_name);
	}
	printf("\r\n");

    return 0;
}

int touch_cmd(char **args)
{
	int fd;

    fd = open(args[1], O_CREAT, 0644);
	if (fd == -1) {
		printf("open error\r\n");
		return -1;
	}

    return 0;
}

struct builtin_cmd builtins[] = {
    {"echo", echo_cmd},
    {"cd", cd_cmd},
    {"pwd", pwd_cmd},
    {"exit", exit_cmd},
    {"mount", mount_cmd},
    {"mkdir", mkdir_cmd},
    {"ls", ls_cmd},
    {"touch", touch_cmd},
    {NULL, NULL}
};

int is_builtin(char *cmd)
{
	int i;

    for (i = 0; builtins[i].name != NULL; i++) {
        if (strcmp(cmd, builtins[i].name) == 0) {
            return 1;
        }
    }
    return 0;
}

int execute_builtin(char **args)
{
	int i;

    for (i = 0; builtins[i].name != NULL; i++) {
        if (strcmp(args[0], builtins[i].name) == 0) {
            return builtins[i].func(args);
        }
    }
    return 0;
}

int execute_external(char **args)
{
	struct dirent dirent[32];
	int i, dir_num;
	int ret = 0;
	int fd;

	strcpy(command_file, "/bin/");
	printf("this is %s(): %d >>> command_file = %s\r\n", __func__, __LINE__, command_file);

	fd = open("/bin", 0 , 0);
	printf("this is %s(): %d >>> fd = %d\r\n", __func__, __LINE__, fd);

	dir_num = readdir(fd, dirent);
	for (i = 0; i < dir_num; i++) {
		// printf("command: %s, d_name: %s\r\n", command, dirent[i].d_name);
		if (strcmp(args[0], dirent[i].d_name) == 0) {
			strcat(command_file, args[0]);
			printf("this is %s(): %d >>> command_file = %s\r\n", __func__, __LINE__, command_file);

			ret = fork();
			if (ret) {
				printf("parent(%d)\r\n", ret);
				printf("this is %s(): %d >>> command_file = %s\r\n", __func__, __LINE__, command_file);

		        waitpid(ret, NULL, 0);
				return ret;
			} else {
				printf("child(%d)\r\n", ret);
				printf("this is %s(): %d >>> command_file = %s\r\n", __func__, __LINE__, command_file);

				exec(command_file, args[1]);
			}
		}
	}

	printf("%s: command not found\r\n", args[0]);
	return ret;
}

int handle_redirection(char **args)
{
    int stdin_fd = -1, fd = -1;
    int i = 0;
    
    while (args[i] != NULL) {
        if (strcmp(args[i], "<") == 0) {
			return 1;
        } else if (strcmp(args[i], ">") == 0) {
			// echo mount-test > /mnt/test.txt
			// echo mount-test > /mnt/home/root/test.txt
            if (args[i + 1] == NULL) {
                printf("syntax error: no output file\r\n");
                return -1;
            }

            fd = open(args[i + 1], O_CREAT, 0644);
            if (fd == -1) {
                printf("open error\r\n");
                return -1;
            }

			write(fd, args[i - 1], strlen(args[i - 1]));
			return 1;
        } else if (strcmp(args[i], ">>") == 0) {
			return 1;
        }

        i++;
    }

    return 0;
}

char **parse_args(char *input)
{    
    int i = 0;

	memset(g_argv, 0, MAX_ARGS * sizeof(char *));

	// "  ls\0"
	// "  ls  \0"
	// "  ls  /bin\0"
	// "  ls  /bin  \0"
	// "  ls  /bin  -l\0"
	// "  ls  /bin  -l  \0"
	while (*input != '\0') {
		while (*input == ' ') {
			input++;
		}
		if (*input == '\0') {
			goto out;
		}

		g_argv[i] = input;

		while (*input != ' ' && *input != '\0') {
			input++;
		}

		if (*input == '\0') {
			goto out;
		} else {
			*input = '\0';
		}

		input++;
		i++;
	}

out: 
    return g_argv;
}

int execute_command(char *input)
{
	int ret;
    char **args = parse_args(input);

	int i = 0;
	while (args[i] != 0) {
		printf("this is %s(): %d >>> args[%d] = %s\r\n", __func__, __LINE__, i, args[i]);
		i++;
	}

    ret = handle_redirection(args);
	if (ret == 1)
		return 0;

    if (is_builtin(args[0])) {
        return execute_builtin(args);
    } else {
        return execute_external(args);
    }
}

void print_prompt()
{
	char cwd[MAX_FILENAME];

    if (getcwd(cwd, sizeof(cwd))) {
        printf("root@zhangxu:%s# ", cwd);
    } else {
		printf("Can not get current directory\r\n", __func__, __LINE__);
    }
}

char *read_input()
{
	int i = 0;
	char c;

	do {
		c = getc();
		if (c != '\r') {
			putc(c);
			g_command[i++] = c;
		}	
	} while (c != '\r');

	g_command[i] = '\0';
		
	printf("\r\n");
}

int main()
{
    printf("Welcome to Simple Shell!\r\n");

    while (1) {
        print_prompt();
        
        read_input();
        
        if (strlen(g_command) == 0) {
            continue;
        }
        
        execute_command(g_command);
    }

    return 0;
}

void mmap_test(void)
{
	int i;

#if 0
	printf("this is %s(): %d\r\n", __func__, __LINE__);
	char *data = (char *)0x1000f000;
	*data = 10;
	printf("this is %s(): %d	*data = %d\r\n", __func__, __LINE__, *data);
	printf("this is %s(): %d\r\n", __func__, __LINE__);
#endif

#if 0
#if 1
	int fd;
	void *map_addr;

	fd = open("/home/test.txt", 0 ,0);
	printf("this is %s(): %d >>> fd = %d\r\n", __func__, __LINE__, fd);

	map_addr = mmap(0xa0000000, 32, 0, 0, fd, 0);
	printf("this is %s(): %d >>> map_addr = 0x%x\r\n", __func__, __LINE__, map_addr);

	for (i = 0; i < 32; i++) {
		printf("%c", ((char *)map_addr)[i]);		
	}
	printf("\r\n");

	while (1);
#else
	void *map_addr;

	map_addr = mmap(0xa0000000, 32, -1, 0, -1, 0);

	for (i = 0; i < 32; i++) {
		((char *)map_addr)[i] = i;
	}

	for (i = 0; i < 32; i++) {
		printf("%d ", ((char *)map_addr)[i]);
	}
	printf("\r\n");

	while (1);
#endif
#endif
}
